#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <sys/time.h>

#include <netinet/in.h>
#include <arpa/inet.h>

#include "tcp_flow_ctrl_test.h"

#define SERVER_STR_ADDR "0.0.0.0"

static const uint8_t s_send_buff[SEND_BLOCK_SIZE] = {0};
static uint8_t s_recv_buff[SEND_BLOCK_SIZE] = {0};

int main(int argc, char const *argv[])
{
    int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
    int client_fd = -1;
    int reuse_opt = 1;
    int send_bytes_cnt = 0;
    int recv_bytes_cnt = 0;

    int ret = setsockopt(listen_fd, SOL_SOCKET, SO_REUSEPORT, &reuse_opt, sizeof(struct sockaddr));
    if(0 != ret)
    {
        PERROR();
        goto fail;
    }
    // int win_size = SOCKET_WIN_SIZE;
    // ret = setsockopt(listen_fd, SOL_SOCKET, SO_RCVBUF, &win_size, sizeof(win_size));
    // if(0 > ret)
    // {
    //     PERROR();
    //     goto fail;
    // }
    // ret = setsockopt(listen_fd, SOL_SOCKET, SO_SNDBUF, &win_size, sizeof(win_size));
    // if(0 > ret)
    // {
    //     PERROR();
    //     goto fail;
    // }

    struct sockaddr_in server_addr =
    {
        .sin_family = AF_INET,
        .sin_addr.s_addr = inet_addr(SERVER_STR_ADDR),
        .sin_port = htons(SERVER_PORT),
    };
    ret = bind(listen_fd, (struct sockaddr*)&server_addr, sizeof(server_addr));
    if(0 != ret)
    {
        PERROR();
        goto fail;
    }
    ret = listen(listen_fd, 1);
    if(0 != ret)
    {
        PERROR();
        goto fail;
    }
    struct sockaddr client_addr = {0};
    socklen_t client_addr_len = sizeof(client_addr);
    client_fd = accept(listen_fd, &client_addr, &client_addr_len);
    close(listen_fd);
    listen_fd = -1;
    if(0 > client_fd)
    {
        PERROR();
        goto fail;
    }
    PRINTF("accept %s:%d client", inet_ntoa(((struct sockaddr_in*)&client_addr)->sin_addr), ntohs(((struct sockaddr_in*)&client_addr)->sin_port));
    PRINTF("tcp flow control test server start");
    pid_t child_pid = -1;
    child_pid = fork();
    if(0 > child_pid)
    {
        PERROR();
        goto fail;
    }
    else if(0 == child_pid)
    {
        struct timeval start, end, intvl;
        gettimeofday(&start, NULL);
        while (recv_bytes_cnt < (SEND_BLOCK_SIZE * SEND_BLOCK_NUM))
        {
            int bytes_in_block = 0;
            while(bytes_in_block < SEND_BLOCK_SIZE)
            {
                int bytes = recv(client_fd, s_recv_buff + bytes_in_block, SEND_BLOCK_SIZE - bytes_in_block, 0);
                if(bytes > 0)
                {
                    bytes_in_block += bytes;
                    recv_bytes_cnt += bytes;
                }
                else if(0 == bytes)
                {
                    PRINTF("client close socket");
                    return EXIT_SUCCESS;
                }
                else
                {
                    PERROR();
                    return EXIT_SUCCESS;
                }
            }
        }
        gettimeofday(&end, NULL);
        timersub(&end, &start, &intvl);
        PRINTF("sum of recv %d bytes spend %ld.%ld Sec", recv_bytes_cnt, intvl.tv_sec, intvl.tv_usec / 1000);
        return EXIT_SUCCESS;
    }
    else
    {
        struct timeval start, end, intvl;
        gettimeofday(&start, NULL);
        while (send_bytes_cnt < (SEND_BLOCK_SIZE * SEND_BLOCK_NUM))
        {
            int bytes_in_block = 0;
            while(bytes_in_block < SEND_BLOCK_SIZE)
            {
                int bytes = send(client_fd, s_send_buff + bytes_in_block, SEND_BLOCK_SIZE - bytes_in_block, 0);
                if(bytes > 0)
                {
                    bytes_in_block += bytes;
                    send_bytes_cnt += bytes;
                }
                else if(0 == bytes)
                {
                    PRINTF("send 0 bytes");
                }
                else
                {
                    PERROR();
                    goto fail;
                }
            }
            usleep(SERVER_SEND_INVL_USEC);
        }
        gettimeofday(&end, NULL);
        timersub(&end, &start, &intvl);
        PRINTF("sum of send %d bytes spend %ld.%ld Sec", send_bytes_cnt, intvl.tv_sec, intvl.tv_usec / 1000);
    }

    int wstatue;//no handle
    pid_t wait_pid = wait(&wstatue);
    if(-1 == wait_pid)
    {
        PERROR();
        goto fail;
    }

    PRINTF("tcp flow control test server end");

    close(client_fd);
    return EXIT_SUCCESS;
fail:
    if(listen_fd > 0)
    {
        close(listen_fd);
    }
    if(client_fd > 0)
    {
        close(client_fd);
    }
    return EXIT_FAILURE;
}
